/*
 *  sphere.cpp
 *  Ray Tracer
 *
 *  Created by Chirantan Ekbote on 10/8/10.
 *  Copyright 2010 Ohio State University. All rights reserved.
 *
 */

#include "../include/sphere.h"
#include "../include/object.h"		// Include here because we used forward declaration in header

int sphereHit(const sphere* sp, const ray* r, double* t) {
	vec3 temp = vec3Sub(r->origin, sp->center);
	
	double a = vec3Dot(r->direction, r->direction);
	double b = 2.0f * vec3Dot(r->direction, temp);
	double c = vec3Dot(temp, temp) - (sp->radius*sp->radius);
	double d = (b*b) - (4.0f*a*c);
	
	// Check to see if ray intersects sphere
	if (d > 0) {
		d = sqrt(d);
		double time = (-b - d) / (2.0f * a);
		
		// Check for valid interval
		if (time < TMIN) {
			time = (-b + d) / (2.0f * a);
		}
		if (time < TMIN || time > TMAX) {
			return 0;	// Invalid time
		}
		
		// We have a valid hit
		*t = time;
		return 1;
	}
	return 0;	// Miss
}

rgb sphereOnHit(const sphere* sp, const ray* r, const vec3 hitPos, const struct object* shapes,
				const light* lights, int depth, const param* p) {
	rgb c, texColor, refractColor, color = rgbScalarMul(sp->ambientColor, AMBIENT_KA);
	vec3 u, v, n, reflectance, normal = vec3UnitVector(vec3Sub(hitPos, sp->center));
	ray shadowRay;
	
	// variables to keep track of time and index of intersected object
	int index;
	double time;
	
	// Loop through lights and add in color
	int i, j;
	if (p->shadows == 2) {	// Soft shadows
		for (i = 1; i <= lights[0].len; i++) {
			// Compute u, v, and n vectors
			n = vec3Neg(vec3UnitVector(vec3Sub(lights[i].pos, hitPos)));
			v = makeVec3(n.x, n.y, 1.0f);
			u = vec3UnitVector(vec3Cross(v, n));
			v = vec3UnitVector(vec3Cross(n, u));
			
			// reset temp color
			c = makeRGB(0.0f, 0.0f, 0.0f);
			
			// Cast shadow rays to area light
			for (j = 0; j < NUM_SUPERSAMPLES; j++) {
				// get the shadowray direction and origin
				shadowRay.direction = vec3UnitVector(vec3Sub(vec3Jitter(lights[i].pos, u, v, n, 
																		AREA_LIGHT_RADIUS, 
																		AREA_LIGHT_RADIUS), 
															 hitPos));
				shadowRay.origin = vec3Add(hitPos, vec3Mul(shadowRay.direction, EPSILON));
				
				// check to see if we are in shadow
				if (!castRay(&shadowRay, shapes, &index, &time)) { 
					// Add diffuse color
					if (sp->useTexture) {
						texColor = makeRGBvec3(vec3Div(vec3Sub(hitPos, 
															   vec3Sub(sp->center, 
																	   makeVec3(sp->radius, 
																				sp->radius, 
																				sp->radius))), 
													   2*sp->radius));
					} else {
						texColor = sp->diffuseColor;
					}
					
					c = rgbAdd(c, rgbScalarMul(texColor, vec3Dot(normal, shadowRay.direction)));
					
					// compute reflectance (r = 2(N.L)N - L)
					reflectance = vec3Sub(vec3Mul(normal, 
												  2.0f * vec3Dot(shadowRay.direction, normal)), 
										  shadowRay.direction);
					
					// Add specular color (s = (V.R)^S * {1, 1, 1})
					c = rgbAdd(c, rgbScalarMul(lights[i].color, 
											   pow(vec3Dot(vec3Neg(r->direction), 
														   reflectance), SHININESS)));
				}
			}
			// Normalize color and add
			color = rgbAdd(rgbScalarDiv(c, NUM_SUPERSAMPLES), color);
		}
	} else if (p->shadows == 1) {	// Hard shadows
		for (i = 1; i <= lights[0].len; i++) {			
			// Compute shadow ray
			shadowRay.direction = vec3UnitVector(vec3Sub(lights[i].pos, hitPos));
			shadowRay.origin = vec3Add(hitPos, vec3Mul(shadowRay.direction, EPSILON));
			
			// check to see if we are in shadow
			if (!castRay(&shadowRay, shapes, &index, &time)) { 
				// Add diffuse color
				if (sp->useTexture) {
					texColor = makeRGBvec3(vec3Div(vec3Sub(hitPos, 
														   vec3Sub(sp->center, 
																   makeVec3(sp->radius, 
																			sp->radius, 
																			sp->radius))), 
												   2*sp->radius));
				} else {
					texColor = sp->diffuseColor;
				}
				
				color = rgbAdd(color, rgbScalarMul(texColor, vec3Dot(normal, 
																	 shadowRay.direction)));
				
				// compute reflectance (r = 2(N.L)N - L)
				reflectance = vec3Sub(vec3Mul(normal, 
											  2.0f * vec3Dot(shadowRay.direction, normal)), 
									  shadowRay.direction);
				
				// Add specular color (s = (V.R)^S * {1, 1, 1})
				color = rgbAdd(color, rgbScalarMul(makeRGB(1.0f, 1.0f, 1.0f), 
												   pow(vec3Dot(vec3Neg(r->direction), 
															   reflectance), SHININESS)));
			}
		}		
	} else {	// No shadows
		for (i = 1; i <= lights[0].len; i++) {			
			// Compute shadow ray
			shadowRay.direction = vec3UnitVector(vec3Sub(lights[i].pos, hitPos));
			shadowRay.origin = vec3Add(hitPos, vec3Mul(shadowRay.direction, EPSILON));
			
			// Ensure that the shadow ray isn't going through the object itself
			if (vec3Dot(shadowRay.direction, normal) > 0) {
				// Add diffuse color
				if (sp->useTexture) {
					texColor = makeRGBvec3(vec3Div(vec3Sub(hitPos, 
														   vec3Sub(sp->center, 
																   makeVec3(sp->radius, 
																			sp->radius, 
																			sp->radius))), 
												   2*sp->radius));
				} else {
					texColor = sp->diffuseColor;
				}
				
				color = rgbAdd(color, rgbScalarMul(texColor, vec3Dot(normal, 
																	 shadowRay.direction)));
				
				// compute reflectance (r = 2(N.L)N - L)
				reflectance = vec3Sub(vec3Mul(normal, 
											  2.0f * vec3Dot(shadowRay.direction, normal)), 
									  shadowRay.direction);
				
				// Add specular color (s = (V.R)^S * {1, 1, 1})
				color = rgbAdd(color, rgbScalarMul(makeRGB(1.0f, 1.0f, 1.0f), 
												   pow(vec3Dot(vec3Neg(r->direction), 
															   reflectance), SHININESS)));
			}
		}
	}

	
	
	// Calculate reflection  and refraction if necessary
	if (depth + 1 < MAXDEPTH) {
		float NdotV = vec3Dot(normal, r->direction);
		if (NdotV < 0) {	// Going into, not out of, sphere
			ray reflectionRay;
			
			// Compute reflection ray (r = -2(V.N)N + V)
			reflectionRay.direction = vec3Add(vec3Mul(normal, -2.0f*vec3Dot(r->direction, 
																			normal)), 
											  r->direction);
			reflectionRay.direction = vec3UnitVector(reflectionRay.direction);
			reflectionRay.origin = vec3Add(hitPos, vec3Mul(reflectionRay.direction, EPSILON));
			
			// Cast the ray and get color
			if (castRay(&reflectionRay, shapes, &index, &time)) {
				color = rgbAdd(color, rgbScalarMul(objectOnHit(&(shapes[index]), &reflectionRay, 
															   rayPointAt(&reflectionRay, time), 
															   shapes, lights, depth+1, p), 
												   objectShininess(&(shapes[index]))));
			}
		}
		
		// Calculate Fresnel refraction
		if (sp->transparency > 0) {
			ray refractionRay;
			float cos = 0.0f;
			
			if (NdotV < 0) { // Going into medium
				// Get direction
				getRefractionRay(normal, r->direction, sp->refractIndex, &refractionRay);
				
				// Get origin
				refractionRay.origin = vec3Add(hitPos, vec3Mul(refractionRay.direction,EPSILON));
				
				// Cast ray
				if (castRay(&refractionRay, shapes, &index, &time)) {
					refractColor = objectOnHit(&(shapes[index]), &refractionRay, 
											   rayPointAt(&refractionRay, time), shapes, lights, 
											   depth+1, p);
				} else {
					refractColor = makeRGB(0.0f, 0.0f, 0.0f);
				}
				
				cos = -NdotV;
			} else {	// Going out of medium
				// Get direction if there is refraction
				if (getRefractionRay(vec3Neg(normal), r->direction, 1.0f / sp->refractIndex, 
									 &refractionRay)) {
					// Get origin
					refractionRay.origin = vec3Add(hitPos, vec3Mul(refractionRay.direction, 
																   EPSILON));
					
					// Cast ray
					if (castRay(&refractionRay, shapes, &index, &time)) {
						
						refractColor = objectOnHit(&(shapes[index]), &refractionRay, 
												   rayPointAt(&refractionRay, time), shapes, 
												   lights, depth+1, p);
					} else {
						refractColor = makeRGB(0.0f, 0.0f, 0.0f);
					}
					
					cos = vec3Dot(refractionRay.direction, normal);
					color = refractColor;
				} else {	// We have total internal refraction
					refractColor = color;
				}
				
			}
			
			float r = sp->r0 + ( (1-sp->r0) * pow(1-cos, 5));
			refractColor = rgbAdd(rgbScalarMul(color, r), rgbScalarMul(refractColor, 1-r));
			color = rgbAdd(rgbScalarMul(color, 1.0f - sp->transparency), 
						   rgbScalarMul(refractColor, sp->transparency));
		}
		
	}
	
	return color;
}





